home *** CD-ROM | disk | FTP | other *** search
/ Interactive Web Graphics with Shout 3D / Interactive Web Graphics With Shout 3D.iso / mac / Shout3Ddemo / Shout3d_runtime / codebase / applets / WalkPanel.java < prev    next >
Text File  |  2000-11-09  |  36KB  |  1,018 lines

  1. /**    
  2.     Company:        Eyematic Interfaces
  3.     Project:        Shout3D 2.0 Sample Code
  4.     Class:            WalkPanel
  5.     Date:            September 15, 1999
  6.     Description:    Class for Walking
  7.     (C) Copyright Eyematic Interfaces, Inc. - 1997-2000 - All rights reserved
  8.  */
  9.  
  10. package applets;
  11.  
  12. import shout3d.core.*;
  13. import shout3d.math.*;
  14. import shout3d.*;
  15.  
  16. /**
  17.  * Shout3D WalkPanel. This class provides the user with the ability to 
  18.  * navigate around a 3D world in a similar manner to the VRML "WALK" mode.
  19.  * 
  20.  * Left Mouse Button:
  21.  * -- click and drag to WALK.  Up/down motion moves forward/backward.  Left/right motion 
  22.  *    rotates left/right.
  23.  * -- <Shift>+drag to walk at DOUBLE SPEED.
  24.  * -- <Control>+drag to TEMPORARILY LOOK AROUND. Rotates the camera relative to the 
  25.  *    forward direction.  Here left/right rotates left/right, and up/down rotates up/down. 
  26.  *    As soon as you release the <control> key, the camera snaps back to face forward.
  27.  * 
  28.  * Right Mouse Button:
  29.  * -- Works same as <Control>+drag of left mouse button.
  30.  * 
  31.  * 
  32.  * Features:
  33.  * -- collision detection: Camera will collide with and slide along walls.  True whether
  34.  * or not terrain following is on.
  35.  * -- terrain following and gravity:  By default, camera will follow the terrain and drop 
  36.  * via gravity if too high above ground.  Terrain following may be turned off via an 
  37.  * applet parameter
  38.  * -- camera space may be rotated: The up direction is always based on the local up 
  39.  * direction of the camera, so cameras can walk on walls if the transforms above them
  40.  * turn them sideways.
  41.  * -- robust:  The applet will continue to work properly if the camera or scene is changed
  42.  * 
  43.  * 
  44.  * Applet Parameters:
  45.  * The following describes applet parameters and their affects:
  46.  * 
  47.  * avatarHeight, collideHeight, avatarRadius:
  48.  * These are always expressed as lengths in world space.
  49.  * -- avatarHeight (default 2)
  50.  *    how far the camera is placed above the ground.
  51.  *    When terrainFollowing, this adjusts the camera vertically to hug the ground.
  52.  *    When not terrainFollowing, the height stays constant in the camera's local space.
  53.  * -- collideHeight (default .25)
  54.  *    Collisions with walls are done in the plane that lies at the 
  55.  *    level of "collideHeight."  When terrainFollowing, collideHeight implies
  56.  *    the maximum height of what you can step over.
  57.  * -- avatarRadius (default 2)
  58.  *    the closest the camera's collide point may get to a wall.
  59.  *
  60.  * speed controls:
  61.  * These control how fast the camera moves in response to mouse drags.
  62.  * -- forwardDragSpeed (default .05)
  63.  *    Specifies amount of forward velocity per pixel moved. So dragging 100 pixels
  64.  *    moves at 5 units/second.
  65.  * -- rotateDragSpeed (default .0025)
  66.  *    Specifies amount of rotational velocity per pixel moved. So dragging 100 pixels
  67.  *    rotates at .25 radians/second.
  68.  * 
  69.  * terrain following controls:
  70.  * -- terrainFollowing (default true)
  71.  *    If true, then the height is kept to be avatarHeight above the ground, where the
  72.  *    ground is considered  the point of intersection that lies below the camera (where 
  73.  *    "below" means downward in the camera's local space).  If the camera is navigated 
  74.  *    to a point higher than that above the ground, it will drop based on the value of 
  75.  *    "gravity."  If terrainFollowing is false, then the camera just remains at a constant 
  76.  *    height of avatarHeight in local space and gravity is ignored.
  77.  * -- gravity (default -9.8 units/sec-squared)
  78.  *    If terrainFollowing is true, this controls how quickly the camera will drop when it 
  79.  *    is higher than avatarHeight above the ground.  If terrainFollowing is false, this is 
  80.  *    ignored.
  81.  * -- maxClimbAngle (default 0.785 radians or about 45 degrees)
  82.  *    If terrainFollowing is true, slopes less than this amount may be climbed (i.e., you 
  83.  *    can walk up them) and higher slopes are treated as walls.  If terrainFollowing is 
  84.  *    false, this is ignored.
  85.  * 
  86.  * @author Paul Isaacs
  87.  * @author Jim Stewartson
  88.  * @author Rory Lane Lutter
  89.  * @author Dave Westwood
  90.  */
  91.  
  92. public class WalkPanel extends Shout3DPanel implements DeviceObserver{
  93.     
  94.     ///////////////////////////////////////////////////////////////////
  95.     // Values of parameters that control the applets behavior.
  96.     // These may be changed with applet parameters:
  97.     // See top of file for descriptions of the applet parameters.
  98.     ///////////////////////////////////////////////////////////////////
  99.     // avatarRadius, avatarHeight, and collideHeight
  100.     public float avatarHeight = 2f;
  101.     public float collideHeight = .25f;
  102.     public float avatarRadius = 2f;
  103.     // speed controls.
  104.     public float forwardDragSpeed = .05f;
  105.     public float rotateDragSpeed = .0025f;
  106.     // Terrain following controls.
  107.     public boolean terrainFollowing = true;
  108.     public float   gravity          = -9.8f;
  109.     public float   maxClimbAngle    = 0.785f;
  110.  
  111.     ///////////////////////////////////////////////////////////////////
  112.     // Public methods to call to control the behavior
  113.     ///////////////////////////////////////////////////////////////////
  114.     /**
  115.      * call this whenever you want to go reset the camera.
  116.      * Result is that camera goes back to its initial orientation and position.
  117.      */
  118.     public void resetCamera(){
  119.         wantToReset = true;
  120.     }
  121.     
  122.     /**
  123.      * Get the forward drag speed
  124.      */
  125.     public float getForwardDragSpeed(){
  126.         return forwardDragSpeed;
  127.     }
  128.     /**
  129.      * Set the forward drag speed
  130.      */
  131.     public void setForwardDragSpeed(float newSpeed){
  132.         forwardDragSpeed = newSpeed;
  133.     }
  134.     
  135.     /**
  136.      * Get the heading drag speed
  137.      */
  138.     public float getRotateDragSpeed(){
  139.         return rotateDragSpeed;
  140.     }
  141.     /**
  142.      * Set the heading drag speed
  143.      */
  144.     public void setRotateDragSpeed(float newSpeed){
  145.         rotateDragSpeed = newSpeed;
  146.     }
  147.     
  148.     ///////////////////////////////////////////////////////////////////
  149.     // Protected member variables used in doing calculations
  150.     ///////////////////////////////////////////////////////////////////
  151.     
  152.     // Info about the camera, the scene, and the camera's place in the scene.
  153.     Viewpoint camera;
  154.     Transform root;
  155.     Node[]    pathToCameraParent = null;    
  156.     
  157.     // initial camera information and whether there is a request to reset it.
  158.     boolean wantToReset = false;
  159.     boolean gotInitCamera = false;        
  160.     float   initCameraHeading;
  161.     float[] initLocalCameraPosition = new float[3];
  162.  
  163.     // The current state of the camera:
  164.     float   cameraHeading = 0;
  165.     float[] worldCameraPosition = new float[3];
  166.     float   verticalVelocity = 0;    // Only used when terrainFollowing
  167.     float   headingSpeed = 0;
  168.     float   forwardSpeed = 0;
  169.     // For temporarily rotating head relative to the official cameraHeading
  170.     // when the <control> key is pressed and the mouse is then dragged
  171.     float    offsetHeading = 0;
  172.     float    offsetPitch   = 0;
  173.     float    offsetHeadingSpeed = 0;
  174.     float    offsetPitchSpeed   = 0;
  175.     boolean wasControlKeyDown   = false;
  176.                                    
  177.     // stored for calculating speeds based on drag distance.
  178.     float startMouseX = 0;
  179.     float startMouseY = 0;
  180.  
  181.     // For converting points and directions between camera and world spaces.
  182.     // Updated by updateConversionMatrices() & updateCameraQuat()
  183.     float[] cameraToWorldMatrix = new float[16];
  184.     float[] worldToCameraMatrix = new float[16];
  185.     // quaternion for the camera
  186.     Quaternion cameraQuat = new Quaternion();
  187.  
  188.     // Worldspace representations of camera's directions.
  189.     // Updated by updateUpForwardLeft()
  190.     float[] worldUp         = new float[3];
  191.     float[] worldForward = new float[3];
  192.     float[] worldLeft    = new float[3];
  193.     // Multiply this by a height in worldspace to find same height expressed 
  194.     // in camera's local space.  Updated by updateUpForwardLeft()
  195.     float   worldToCameraHeightMultiplier = 1f;
  196.     
  197.     // These contain info about picks in 3 important directions.
  198.     Picker downwardPicker  = null;    // To find ground below.
  199.     Picker alongGroundPicker = null;  // To find walls ahead/behind.
  200.     Picker alongWallPicker = null;    // To find limits to deflection along wall.
  201.     // These are the points from which the picks are done for the second two 
  202.     // pickers.  The downwardPicker just picks down from worldCameraPosition.
  203.     float[] alongWallPickerFrom = { 0, 0, 0 };
  204.     float[] alongGroundPickerFrom = { 0, 0, 0 };
  205.  
  206.     // Directions of travel used when attempting to move forward/backward
  207.     // or deflect off walls.
  208.     //
  209.     // The direction of travel (forward or backward depending on speed), 
  210.     // adjusted by the slope of the ground when terrain following.
  211.     float[] alongGroundDir = { 0, 0, 0 };
  212.     // Set during updateAlongGroundDir.  True if the alongGroundDir is 
  213.     // climbing a slope, false in all other cases.
  214.     boolean isClimbing = false;
  215.     // If deflected against a wall, this is the direction of travel parallel to the wall
  216.     float[] alongWallDir = { 1, 0, 0 };
  217.     
  218.     // Used here and there as temp storage
  219.     float[] tempVec = new float[3];
  220.     
  221.     ///////////////////////////////////////////////////////////////////
  222.     // Standard panel methods
  223.     ///////////////////////////////////////////////////////////////////
  224.     /**
  225.      * Construct a WalkPanel
  226.      * 
  227.      * @param applet the Shout3DApplet in which this panel is to be drawn
  228.      */
  229.     public WalkPanel(Shout3DApplet applet){
  230.         super(applet);
  231.     }
  232.     /**
  233.      * Constructs a WalkPanel
  234.      * 
  235.      * @param applet the Shout3DApplet in which this panel is to be drawn
  236.      * @param width the width of the panel in pixels
  237.      * @param height the height of the panel in pixels
  238.      */
  239.     public WalkPanel(Shout3DApplet applet, int width, int height){
  240.         super(applet,width,height);
  241.     }
  242.  
  243.     /**
  244.      * Constructs a WalkPanel
  245.      * 
  246.      * @param applet the Shout3DApplet in which this panel is to be drawn
  247.      * @param x the x position of the panel in pixels
  248.      * @param y the x position of  the panel in pixels
  249.      * @param width the width of the panel in pixels
  250.      * @param height the height of the panel in pixels
  251.      */
  252.     public WalkPanel(Shout3DApplet applet, int x, int y, int width, int height){
  253.         super(applet,x,y,width,height);
  254.     }
  255.  
  256.     /**
  257.      * Remove observers when done with the panel
  258.      */
  259.     public void finalize()throws Throwable {
  260.         applet.getDeviceListener().removeDeviceObserver(this, "DeviceInput");
  261.         applet.getRenderer().removeRenderObserver(this);        
  262.         super.finalize();
  263.     }
  264.     
  265.     /**
  266.      * Overrides Shout3DPanel.customInitialize()
  267.      * 
  268.      * Reads in all the applet parameters.
  269.      * Registers to observe deviceInputs and rendering.
  270.      */
  271.     public void customInitialize() {
  272.         
  273.         // Read the 3 avatar parameters from applet parameters, if specified:
  274.         String avatarHeightString = applet.getParameter("avatarHeight");
  275.         if (avatarHeightString != null){
  276.             avatarHeight = Float.valueOf(avatarHeightString).floatValue();
  277.         }
  278.         String collideHeightString = applet.getParameter("collideHeight");
  279.         if (collideHeightString != null){
  280.             collideHeight = Float.valueOf(collideHeightString).floatValue();
  281.         }
  282.         String avatarRadiusString = applet.getParameter("avatarRadius");
  283.         if (avatarRadiusString != null){
  284.             avatarRadius = Float.valueOf(avatarRadiusString).floatValue();
  285.         }
  286.  
  287.         // Read the 2 drag speed parameters, if specified:
  288.         String forwardDragSpeedString = applet.getParameter("forwardDragSpeed");
  289.         if (forwardDragSpeedString != null){
  290.             forwardDragSpeed = Float.valueOf(forwardDragSpeedString).floatValue();
  291.         }    
  292.         String rotateDragSpeedString = applet.getParameter("rotateDragSpeed");
  293.         if (rotateDragSpeedString != null){
  294.             rotateDragSpeed = Float.valueOf(rotateDragSpeedString).floatValue();
  295.         }    
  296.     
  297.         // Read the 3 terrain following control parameters, if specified.
  298.         String terrainString = applet.getParameter("terrainFollowing");
  299.         if (terrainString != null && terrainString.toLowerCase().equals("true")){
  300.             terrainFollowing = true;
  301.         }    
  302.         String gravityString = applet.getParameter("gravity");
  303.         if (gravityString != null){
  304.             gravity = Float.valueOf(gravityString).floatValue();
  305.         }    
  306.         String maxClimbAngleString = applet.getParameter("maxClimbAngle");
  307.         if (maxClimbAngleString != null){
  308.             maxClimbAngle = Float.valueOf(maxClimbAngleString).floatValue();
  309.         }
  310.  
  311.         // register for device events
  312.         getDeviceListener().addDeviceObserver(this, "DeviceInput", null);    
  313.         // register for render events
  314.         getRenderer().addRenderObserver(this, null);
  315.         
  316.     }
  317.     
  318.     /**
  319.      * Watch the device inputs:
  320.      * Set the headingSpeed and forwardSpeed based on
  321.      * mouse movement, rotateDragSpeed, and forwardDragSpeed.
  322.      */
  323.     public boolean onDeviceInput(DeviceInput di, Object userData){
  324.         if (di instanceof MouseInput){
  325.             MouseInput mi = (MouseInput)di;
  326.             boolean isControlKeyDown = ((mi.modifiers & DeviceInput.CTRL_MASK) != 0);
  327.             switch (mi.which){
  328.             case MouseInput.DOWN:
  329.                 dragStart(mi);
  330.                 break;
  331.             case MouseInput.DRAG:
  332.                 // Right mouse button is always "look around" but
  333.                 // with left button, control key shifts modes.
  334.                 // So only restart if using left button and changing
  335.                 // control key status
  336.                 if (mi.button == 0 && isControlKeyDown != wasControlKeyDown){
  337.                     // Do a restart to change modalities, then continue on
  338.                     dragFinish(mi);
  339.                     dragStart(mi);
  340.                     wasControlKeyDown = isControlKeyDown;
  341.                 }
  342.                 dragMiddle(mi);
  343.                 break;
  344.             case MouseInput.UP:
  345.                 dragFinish(mi);
  346.                 break;
  347.                 
  348.             }
  349.         }
  350.         // return false, let other entities handle the events too.
  351.         return false;
  352.     }
  353.     
  354.     protected void dragStart(MouseInput mi) {
  355.         startMouseX = mi.x;
  356.         startMouseY = mi.y;
  357.     }
  358.     protected void dragMiddle(MouseInput mi) {
  359.         // Left mouse without control key down is regular walk mode
  360.         if (mi.button == 0 && !wasControlKeyDown){
  361.             // Regular motion
  362.             if ((mi.modifiers & DeviceInput.SHIFT_MASK) != 0){
  363.                 // go fast
  364.                 headingSpeed = -(mi.x-startMouseX)*rotateDragSpeed*2;                                
  365.                 forwardSpeed = -(mi.y-startMouseY)*forwardDragSpeed*2;                      
  366.             }
  367.             else {
  368.                 // otherwise go normal speed
  369.                 headingSpeed = -(mi.x-startMouseX)*rotateDragSpeed;
  370.                 forwardSpeed = -(mi.y-startMouseY)*forwardDragSpeed;                      
  371.             }
  372.         }
  373.         else {
  374.             // Right mouse or control key down is look-around mode.
  375.             
  376.             // Offset motion of camera pitch and heading.
  377.             offsetHeadingSpeed = -(mi.x-startMouseX)*rotateDragSpeed;
  378.             offsetPitchSpeed   = -(mi.y-startMouseY)*rotateDragSpeed;                      
  379.         }
  380.     }
  381.     protected void dragFinish(MouseInput mi) {    
  382.         headingSpeed = 0;
  383.         forwardSpeed = 0;
  384.         offsetHeading = 0;
  385.         offsetPitch   = 0;
  386.         offsetHeadingSpeed = 0;
  387.         offsetPitchSpeed   = 0;
  388.     }
  389.     
  390.     ///////////////////////////////////////////////////////////////////
  391.     // Methods for performing navigation.
  392.     ///////////////////////////////////////////////////////////////////
  393.  
  394.  
  395.     /**
  396.      * 
  397.      * High-level description:
  398.      * 
  399.      * Each time the panel renders, this method will:
  400.      * -- re-initialize the camera if needed
  401.      * -- update the heading orientation
  402.      * -- move forward/backward as far as possible
  403.      * -- deflect off a wall if it couldn't go forward/backward all the way
  404.      * -- adjust the height based on avatarHeight and/or gravity.
  405.      * 
  406.      * Note: no onPostRender() method is implemented here because it's
  407.      * implemented in the super class and is not required here.
  408.      * 
  409.      */
  410.     public void onPreRender(Renderer r, Object userData){
  411.         super.onPreRender(r, userData);
  412.         
  413.         // Initialize if necessary.
  414.         checkCamera();
  415.         
  416.         // adjust the camera heading according to user input
  417.         if (headingSpeed != 0f) {
  418.             cameraHeading += headingSpeed/getFramesPerSecond();
  419.         }
  420.         // adjust the offset heading and pitch according to use input
  421.         if (offsetHeadingSpeed != 0f) {
  422.             offsetHeading += offsetHeadingSpeed/getFramesPerSecond();
  423.         }
  424.         if (offsetPitchSpeed != 0f) {
  425.             offsetPitch += offsetPitchSpeed/getFramesPerSecond();
  426.             // Clamp this to +/- 90 degrees
  427.             if (offsetPitch > 1.57f)
  428.                 offsetPitch = 1.57f;
  429.             if (offsetPitch < -1.57f)
  430.                 offsetPitch = -1.57f;
  431.         }
  432.         // Set the orientation field based on updated values.
  433.         updateViewpointOrientationField();
  434.  
  435.         // Insure that conversion matrices and important directions are up to date.
  436.         updateConversionMatrices();
  437.         updateCameraQuat();
  438.         updateUpForwardLeft();
  439.         
  440.         // How far do we want to go:
  441.         float   distance = (float)(forwardSpeed/getFramesPerSecond());
  442.         
  443.         // Before trying to move camera, get worldCameraLocation by 
  444.         // converting position field to world space.
  445.         // Normally not required, but if the camera lies within a moving coordinate
  446.         // system, this will keep us up to date with where it's been moved.
  447.         convertVecFromTo(cameraToWorldMatrix,camera.position.getValue(),worldCameraPosition);
  448.         
  449.         // Tries to move along ground.
  450.         // Different behavior depending on value of terrainFollowing.
  451.         boolean isForward = (distance >= 0);
  452.         if (!isForward)
  453.             distance *= -1;
  454.         // This always returns a positive number.
  455.         float distanceMoved = moveCameraAlongGround(isForward, distance);
  456.         
  457.         if (distanceMoved < distance){
  458.             // Movement was blocked by a wall.
  459.             // Try to deflect and slide parallel to the wall.
  460.             deflectCameraAlongWall(distance - distanceMoved);
  461.         }
  462.         
  463.         // Adjust camera up/down.  Different behavior depending on value of terrainFollowing
  464.         // and gravity.
  465.         adjustCameraHeight();
  466.         // Set the camera position based on the new worldCameraPosition.
  467.         updateViewpointPositionField();
  468.     }
  469.  
  470.     /**
  471.      * Check if the camera needs to be re-established.
  472.      * This happens when any of the following occurs:
  473.      * - the scene changes
  474.      * - the bound camera changes.
  475.      * - the path to the camera changes
  476.      * 
  477.      * Then check if wantToReset is true and therefore
  478.      * the camera position and rotation need to be reset.
  479.      * This happens either:
  480.      * - the camera was re-established above, or
  481.      * - resetCamera() was called since last time.
  482.      */
  483.     public void checkCamera(){
  484.     
  485.         // get the current camera and scene root.
  486.         Viewpoint curCam = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint"));
  487.         Transform curRoot = (Transform)getScene();
  488.         
  489.         // Do they differ from cached values, or is pathToCameraParent no longer valid?
  490.         if (curCam != camera || curRoot != root || !isPathValid(pathToCameraParent)){        
  491.             camera = curCam;
  492.             root = curRoot;
  493.             pathToCameraParent = getPathToCameraParent();
  494.             
  495.             gotInitCamera = false;
  496.             
  497.             wantToReset = true;                
  498.         }
  499.         
  500.         // Save the initial camera information if needed
  501.         if (gotInitCamera == false && camera != null){
  502.             // Save initial position of camera in local space
  503.             System.arraycopy(camera.position.getValue(), 0, initLocalCameraPosition, 0, 3);
  504.             
  505.             // Decompose orientation into eulers and save the camera heading.
  506.             // Discard the pitch and roll components.
  507.             Quaternion initQuat = new Quaternion();
  508.             initQuat.setAxisAngle(camera.orientation.getValue());
  509.             float[] initEulers = new float[3];
  510.             initQuat.getEulers(initEulers);
  511.             initCameraHeading = initEulers[0];
  512.             
  513.             gotInitCamera = true;
  514.         }
  515.         
  516.         // Do we want to reset the camera position/orientation?
  517.         if (wantToReset == true){
  518.             // Initialize cameraHeading to its proper current value.
  519.             cameraHeading = initCameraHeading;
  520.  
  521.             // update conversion matrices and important directions
  522.             updateConversionMatrices();
  523.             updateCameraQuat();
  524.             updateUpForwardLeft();
  525.  
  526.             // Convert initial local camera position to current world position
  527.             convertVecFromTo(cameraToWorldMatrix, 
  528.                              initLocalCameraPosition, worldCameraPosition);
  529.             
  530.             // Adjust the height of the worldspace camera.
  531.             // This brings worldCameraPosition to its proper current value.
  532.             adjustCameraHeight();
  533.  
  534.             // avoid doing this again
  535.             wantToReset = false;
  536.         }        
  537.     }
  538.     
  539.     /**
  540.      * Insure that the cameraToWorldMatrix and worldToCameraMatrix are up to date.
  541.      */
  542.     void updateConversionMatrices() {
  543.         cameraToWorldMatrix = MatUtil.getMatrixAlongPath(pathToCameraParent, true);    
  544.         worldToCameraMatrix = MatUtil.getMatrixAlongPath(pathToCameraParent, false);    
  545.     }
  546.     
  547.     /** 
  548.      * Insure that cameraQuat is up to date.
  549.      */
  550.     void updateCameraQuat(){
  551.         cameraQuat.setEulers(cameraHeading, 0, 0);
  552.     }
  553.  
  554.     final float[] localUp      = { 0, 1, 0 };
  555.     final float[] localForward = { 0, 0, -1 };
  556.     /** 
  557.      * Insure that the camera's up, forward and left directions in worldspace are 
  558.      * up to date.  Assumes that updateConversionMatrices() has been called.
  559.      */
  560.     void updateUpForwardLeft(){
  561.         // To transform directions from through-the-camera to worldspace,
  562.         // rotate by cameraQuat, then transform by cameraToWorldMatrix,
  563.         // then normalize result.
  564.  
  565.         System.arraycopy(localForward,0,worldForward,0,3);
  566.         cameraQuat.xform(worldForward);
  567.         convertDirFromTo(cameraToWorldMatrix, worldForward, worldForward );
  568.         MatUtil.normalize(worldForward);
  569.         
  570.         System.arraycopy(localUp,0,worldUp,0,3);
  571.         cameraQuat.xform(worldUp);
  572.         convertDirFromTo(cameraToWorldMatrix, worldUp, worldUp );
  573.         // Normalize the up vector.
  574.         // The length of the up vector scales heights from camera to world space
  575.         float length = MatUtil.normalize(worldUp);
  576.         // The inverse of the length scales heights from world space to camera space.
  577.         worldToCameraHeightMultiplier = 1/length;
  578.         
  579.         crossProduct(worldUp, worldForward, worldLeft );
  580.     }
  581.  
  582.  
  583.     /**
  584.      * Tries to move along the ground by distance.
  585.      * 
  586.      * If no terrainFollowing, will slide along surfaces that are encountered as if they are
  587.      * walls, always staying in same plane of motion.
  588.      * 
  589.      * If terrainFollowing, then maxClimbAngle (an applet parameter) defines the maximum slope of 
  590.      * ground that can be climbed.  Ground can be climbed.  If a slope
  591.      * is too great to be ground, it is treated as wall and sliding occurs against it.
  592.      * 
  593.      * @param isForward true if the camera is trying to move forward relative to its orientation,
  594.      * false if trying to move backward
  595.      * @param desiredDistance how far the camera would like to travel, this is always a positive number
  596.      */
  597.     float moveCameraAlongGround(boolean isForward, float desiredDistance){
  598.         
  599.         // You might think that a downward pick needs to be done via updateDownwardPick() here.
  600.         // But actually, the information is always there from the render before, because 
  601.         // updateDownwardPick() is called during adjustCameraHeight at the end of onPreRender(),
  602.         // as well as during checkCamera() if the camera has changed.
  603.         // So the downwardPicker info is all up to date.
  604.         
  605.         // Sets alongGroundDir based on terrain (if terrainFollowing) or simply camera's
  606.         // forward (or backward) direction if not terrainFollowing.
  607.         updateAlongGroundDir(isForward);
  608.         
  609.         // Uses alongGroundPicker to pick in the alongGroundDir direction from the camera.
  610.         updateAlongGroundPick();
  611.         
  612.         float   actualDistance = 0f;
  613.         if (alongGroundPicker.getPickPath() == null){
  614.             // No obstructions, go entire distance
  615.             actualDistance = desiredDistance;
  616.         }
  617.         else {
  618.             float[] travelHit = alongGroundPicker.getPickInfo(Picker.POINT);
  619.             
  620.             // Find the distance between the alongGroundPickerFrom point and the pick.
  621.             float distToHit = getDistance(alongGroundPickerFrom, travelHit);
  622.             
  623.             if (distToHit >= desiredDistance + avatarRadius)
  624.                 actualDistance = desiredDistance;
  625.             else if (distToHit <= avatarRadius)
  626.                 actualDistance = 0;
  627.             else {
  628.                 // Can only get as close to hit as avatarRadius
  629.                 actualDistance = distToHit - avatarRadius;
  630.             }
  631.         }
  632.         
  633.         for (int i = 0; i < 3; i++)
  634.             worldCameraPosition[i] += actualDistance * alongGroundDir[i];
  635.         
  636.         // Return the distance traveled
  637.         return actualDistance;
  638.     }
  639.     
  640.     /**
  641.     * Used when movement along ground was blocked by a wall.
  642.     * Tries to slide parallel to the wall that was hit, but in plane of ground.
  643.     * 
  644.     * If no terrainFollowing, will just get deflected in ground plane.
  645.     * If terrainFollowing, will follow the direction where the ground plane intersects the 
  646.     * wall, which may slope upward in addition to deflecting sideways
  647.     * 
  648.     * @return the distance that was actually moved.
  649.     */
  650.     float deflectCameraAlongWall(float desiredDistance) {
  651.         
  652.         // Finds the direction of travel parallel to the wall.
  653.         updateAlongWallDir();
  654.         
  655.         // Uses alongWallPicker to pick in the alongWallDir direction from the camera.
  656.         updateAlongWallPick();
  657.         
  658.         float   actualDistance = 0f;
  659.         if (alongWallPicker.getPickPath() == null){
  660.             // No obstructions, go entire distance
  661.             actualDistance = desiredDistance;
  662.         }
  663.         else {
  664.             float[] travelHit = alongWallPicker.getPickInfo(Picker.POINT);
  665.             
  666.             // Find the distance between the alongWallPickerFrom point and the pick.
  667.             float distToHit = getDistance(alongWallPickerFrom, travelHit);
  668.             
  669.             if (distToHit >= desiredDistance + avatarRadius)
  670.                 actualDistance = desiredDistance;
  671.             else if (distToHit <= avatarRadius)
  672.                 actualDistance = 0;
  673.             else {
  674.                 // Can only get as close to hit as avatarRadius
  675.                 actualDistance = distToHit - avatarRadius;
  676.             }
  677.         }
  678.         
  679.         for (int i = 0; i < 3; i++)
  680.             worldCameraPosition[i] += actualDistance * alongWallDir[i];
  681.         
  682.         // Return the distance traveled
  683.         return actualDistance;
  684.     }
  685.     
  686.     /**
  687.      * Adjusts the height of the camera over the ground plane.
  688.      * 
  689.      */
  690.     void adjustCameraHeight(){
  691.         if (!terrainFollowing){
  692.             // Bring camera position into local space, raise by avatarHeight above 
  693.             // groundPlane, convert back out to world space.
  694.             convertVecFromTo(worldToCameraMatrix, worldCameraPosition, tempVec);
  695.             tempVec[1] = avatarHeight * worldToCameraHeightMultiplier;
  696.             convertVecFromTo(cameraToWorldMatrix, tempVec, worldCameraPosition);
  697.         }
  698.         else {
  699.             // Terrain following.
  700.             updateDownwardPick();
  701.             
  702.             // If there's ground below, drop down to it:
  703.             if (downwardPicker.getPickPath() != null){
  704.                 float[] groundPoint = downwardPicker.getPickInfo(Picker.POINT);
  705.  
  706.                 // Do all this stuff with positive signs, it's easier:
  707.                 float heightAboveGround = getDistance(worldCameraPosition, groundPoint);
  708.                 float fallingDist = Math.abs(verticalVelocity/getFramesPerSecond());
  709.                 if ( (fallingDist + avatarHeight) < heightAboveGround ){
  710.                     // Free fall!
  711.                     // Do the fall and accelerate
  712.                     for (int i = 0; i < 3; i++)
  713.                         worldCameraPosition[i] -= fallingDist * worldUp[i];
  714.                     verticalVelocity += gravity/getFramesPerSecond();
  715.                 }
  716.                 else {
  717.                     // The ground is hit.
  718.                     // Place on ground and set verticalVelocity back to 0.
  719.                     for (int i = 0; i < 3; i++)
  720.                         worldCameraPosition[i] = groundPoint[i] + avatarHeight * worldUp[i];
  721.                     verticalVelocity = 0;
  722.                 }
  723.             }
  724.         }
  725.     }
  726.     
  727.     /**
  728.      * When terrain following, uses the downwardPicker to pick down below 
  729.      * the camera.
  730.      */
  731.     void updateDownwardPick(){
  732.         if (!terrainFollowing)
  733.             return;
  734.         
  735.         if (downwardPicker == null){
  736.             downwardPicker = getNewPicker();
  737.             downwardPicker.setPickInfo(Picker.POINT, true);
  738.             downwardPicker.setPickInfo(Picker.NORMAL, true);
  739.         }
  740.         
  741.         // Do this every time, since the scene might have changed.
  742.         downwardPicker.setScene(root);
  743.         
  744.         // Pick from the camera downward.
  745.         for(int i = 0; i < 3; i++)
  746.             tempVec[i] = worldCameraPosition[i] - worldUp[i];
  747.         downwardPicker.pickClosestFromTo(worldCameraPosition, tempVec);
  748.     }
  749.     
  750.     /**
  751.      * Updates alongGroundDir.
  752.      * Different whether terrain following or not.
  753.      * 
  754.      * @param isForward true if camera is moving forward relative to its own POV.
  755.      */
  756.     void updateAlongGroundDir(boolean isForward){
  757.         
  758.         // Start by assuming that we're not climbing a hill...
  759.         isClimbing = false;
  760.  
  761.         // ...and that we'll just go in camera's direction, forward or backward:
  762.         System.arraycopy(worldForward, 0, alongGroundDir, 0, 3);
  763.         if (!isForward){
  764.             for (int i = 0; i < 3; i++)
  765.                 alongGroundDir[i] *= -1;
  766.         }
  767.         
  768.         
  769.         if (!terrainFollowing){
  770.             // When not terrain following, no modification.
  771.             return;
  772.         }
  773.         
  774.         // Terrain following case.
  775.         
  776.         // If no ground, no modification, just float the camera's innate direction of travel.
  777.         if (downwardPicker.getPickPath() == null)
  778.             return;
  779.         
  780.         // If ground is a wall that the camera is moving away from, 
  781.         // just let it keep going, don't modify slope of travel.
  782.         // This will be true if there's a component of the ground normal in the 
  783.         // direction of travel.
  784.         if (MatUtil.dot(downwardPicker.getPickInfo(Picker.NORMAL), alongGroundDir) > 0)
  785.             return;
  786.  
  787.         // If ground is an unclimbable wall, use worldForward.
  788.         // We'll either be able to jump the wall or we'll slam into it and 
  789.         // slide along it.
  790.         if (!isPlaneClimbable(downwardPicker.getPickInfo(Picker.NORMAL)))
  791.             return;
  792.         
  793.         // The floor is angled up in front of us at a climbable angle.
  794.         isClimbing = true;
  795.         
  796.         // Set the direction to be the one within the plane that has a forward sense.
  797.         crossProduct(worldLeft, downwardPicker.getPickInfo(Picker.NORMAL), alongGroundDir);
  798.         MatUtil.normalize(alongGroundDir);
  799.         if (!isForward){
  800.             for (int i = 0; i < 3; i++)
  801.                 alongGroundDir[i] *= -1;
  802.         }
  803.         return;
  804.     }
  805.     
  806.     /**
  807.      * Plane is climbable if the angle between worldUp and its normal 
  808.      * is less than maxClimbAngle
  809.      */
  810.     boolean isPlaneClimbable(float[] planeNormal){
  811.         float cosMaxClimbAngle = (float) Math.cos(maxClimbAngle);
  812.         return (MatUtil.dot(planeNormal, worldUp) >= cosMaxClimbAngle);
  813.     }
  814.  
  815.     /**
  816.      * Uses the alongGroundPicker to pick in alongGroundDir direction from the camera's
  817.      * collide point
  818.      * 
  819.      */
  820.     void updateAlongGroundPick(){
  821.         if (alongGroundPicker == null){
  822.             alongGroundPicker = getNewPicker();
  823.             alongGroundPicker.setPickInfo(Picker.POINT, true);
  824.             alongGroundPicker.setPickInfo(Picker.NORMAL, true);
  825.         }
  826.         
  827.         // Do this every time, since the scene might have changed.
  828.         alongGroundPicker.setScene(root);
  829.         
  830.         // Move from the collidePoint.
  831.         // The point below the camera at collideHeight is used:
  832.         for (int i = 0; i < 3; i++)
  833.             alongGroundPickerFrom[i] = worldCameraPosition[i] + (-avatarHeight + collideHeight)*worldUp[i];
  834.         
  835.         // See what lies ahead in the alongGroundDir
  836.         for (int i = 0; i < 3; i++)
  837.             tempVec[i] = alongGroundPickerFrom[i] + alongGroundDir[i];
  838.         alongGroundPicker.pickClosestFromTo(alongGroundPickerFrom, tempVec);
  839.     }
  840.  
  841.     /**
  842.      * Updates alongWallDir
  843.      * Different whether terrain following or not.
  844.      */
  845.     void updateAlongWallDir(){
  846.  
  847.         if (!isClimbing) {
  848.             // go parallel to wall and parallel to camera's natural ground plane
  849.             crossProduct(worldUp, alongGroundPicker.getPickInfo(Picker.NORMAL), alongWallDir);
  850.         }
  851.         else {
  852.             // go parallel to wall and parallel to the ground we're climbing:
  853.             crossProduct(downwardPicker.getPickInfo(Picker.NORMAL), 
  854.                          alongGroundPicker.getPickInfo(Picker.NORMAL), alongWallDir);
  855.         }
  856.         MatUtil.normalize(alongWallDir);
  857.         
  858.         // Flip sign if the direction chosen is in opposite sense of our alongGroundDir:
  859.         if (MatUtil.dot(alongWallDir, alongGroundDir) < 0){
  860.             for (int i = 0; i < 3; i++)
  861.                 alongWallDir[i] *= -1;
  862.         }
  863.     }
  864.     
  865.     /**
  866.      * Uses the alongWallPicker to pick in alongWallDir direction from the camera.
  867.      * 
  868.      */
  869.     void updateAlongWallPick(){
  870.         if (alongWallPicker == null){
  871.             alongWallPicker = getNewPicker();
  872.             alongWallPicker.setPickInfo(Picker.POINT, true);
  873.             alongWallPicker.setPickInfo(Picker.NORMAL, true);
  874.         }
  875.         
  876.         // Do this every time, since the scene might have changed.
  877.         alongWallPicker.setScene(root);
  878.         
  879.         // Move from the collide point.
  880.         // The point below the camera at collideHeight is used:
  881.         for (int i = 0; i < 3; i++)
  882.             alongWallPickerFrom[i] = worldCameraPosition[i] + (-avatarHeight + collideHeight)*worldUp[i];
  883.         
  884.         // See what lies ahead in the alongWallDir
  885.         for (int i = 0; i < 3; i++)
  886.             tempVec[i] = alongWallPickerFrom[i] + alongWallDir[i];
  887.         alongWallPicker.pickClosestFromTo(alongWallPickerFrom, tempVec);
  888.     }
  889.  
  890.     ///////////////////////////////////////////////////////////////////
  891.     // Methods to set the fields based on current state.
  892.     ///////////////////////////////////////////////////////////////////
  893.     
  894.     /**
  895.      * Transforms the worldCameraPosition to local space,then
  896.      * sets the field value.
  897.      */
  898.     void updateViewpointPositionField(){
  899.         convertVecFromTo(worldToCameraMatrix,worldCameraPosition,camera.position.getValue());
  900.         camera.position.setValue( camera.position.getValue() );
  901.     }
  902.     
  903.     /**
  904.      * Viewpoint's orientation is based solely on the heading.
  905.      */
  906.     void updateViewpointOrientationField(){
  907.         // Set the camera orientation based on the cameraHeading
  908.         cameraQuat.setEulers(cameraHeading+offsetHeading, offsetPitch, 0);
  909.         cameraQuat.getAxisAngle(camera.orientation.getValue());
  910.         camera.orientation.setValue(camera.orientation.getValue());
  911.     }
  912.     
  913.     ///////////////////////////////////////////////////////////////////
  914.     // Simple utility methods for paths, matrices, and vectors.
  915.     ///////////////////////////////////////////////////////////////////
  916.     
  917.     // Distance function
  918.     static float getDistance(float[] from, float[] to){
  919.         float x = from[0] - to[0];
  920.         float y = from[1] - to[1];
  921.         float z = from[2] - to[2];
  922.         float dist = (float)Math.sqrt(x*x + y*y + z*z);
  923.         //System.out.println(dist);
  924.         return dist;
  925.     }
  926.  
  927.     static void crossProduct(float[] vec0, float[] vec1, float[] result){
  928.         result[0] =  vec0[1]*vec1[2] - vec0[2]*vec1[1];
  929.         result[1] = -vec0[0]*vec1[2] + vec0[2]*vec1[0];
  930.         result[2] =  vec0[0]*vec1[1] - vec0[1]*vec1[0];
  931.     }
  932.  
  933.     /**
  934.      * Converts fromVec by the matrix matrix, placing the converted result into
  935.      * toVec.
  936.      * 
  937.      * fromVec and toVec may refer to the same vector.
  938.      * matrix, fromVec and toVec must be pre-allocated.
  939.      */
  940.     static void convertVecFromTo(float[] matrix, float[] fromVec, float[] toVec){
  941.         System.arraycopy(fromVec,0,toVec,0,3);
  942.         MatUtil.multVecMatrix(matrix,toVec);
  943.     }            
  944.  
  945.     /**
  946.      * Converts fromDir by the matrix matrix, placing the converted result into
  947.      * toDir.
  948.      * 
  949.      * fromDir and toDir may refer to the same vector.
  950.      * matrix, fromDir and toDir must be pre-allocated.
  951.      */
  952.     static void convertDirFromTo(float[] matrix, float[] fromDir, float[] toDir){
  953.         System.arraycopy(fromDir,0,toDir,0,3);
  954.         MatUtil.multDirMatrix(matrix,toDir);
  955.     }
  956.     
  957.     /**
  958.      * Returns a path from root to camera's parent, if camera lies under scene.
  959.      * 
  960.      * If no camera is included in the s3d scene, the panel will use a
  961.      * default camera that is not transformed relative to world space.
  962.      * So in this case, null is returned.
  963.      */
  964.     public Node[] getPathToCameraParent(){
  965.         // Set answer to be a path from root down to camera
  966.         Searcher s = getNewSearcher();
  967.         s.setNode(camera);
  968.         Node[] searchPath = s.searchFirst(root);
  969.             
  970.         if (searchPath==null || searchPath.length < 2)
  971.             return null;
  972.         
  973.         // Return a copy of the path with its last node (the viewpoint) removed.
  974.         Node[] answer = new Node[searchPath.length-1];
  975.         System.arraycopy(searchPath,0,answer,0,answer.length);
  976.         return answer;
  977.     }
  978.  
  979.     /**
  980.      * Returns true if path p is a valid path.
  981.      * A valid path is:
  982.      * --  null
  983.      * --  one node long
  984.      * --  or one in which the parent/child relationships are all correct.
  985.      */
  986.     static boolean isPathValid(Node[] p){
  987.         
  988.         // null paths are valid
  989.         if (p == null || p.length < 2)
  990.             return true;
  991.         
  992.         // make sure parent/child relationships still hold.  If not, then
  993.         // reparenting has occured between some of the nodes.
  994.         Node   parent, child;
  995.         Node[] kids;
  996.         // For each parent along the path
  997.         for (int i = 0; i < p.length-2; i++){
  998.             parent = p[i];
  999.             child = p[i+1];
  1000.             // Return false if the child is not in the parent's children field.
  1001.             if ( !(parent instanceof Group) )
  1002.                 return false;
  1003.             kids = ((Group)parent).children.getValue();
  1004.             if (kids == null)
  1005.                 return false;
  1006.             boolean gotIt = false;
  1007.             for (int j = 0; j < kids.length; j++){
  1008.                 if (kids[j] == child)
  1009.                     gotIt = true;
  1010.             }
  1011.             // Return if the child was not in the parent's field
  1012.             if (gotIt == false)
  1013.                 return false;
  1014.         }
  1015.         // Passed all tests, return true.
  1016.         return true;
  1017.     }
  1018. }